package com.wang.tool.delivery;

import com.alibaba.fastjson.JSONObject;
import com.wang.tool.common.Container;
import com.wang.tool.common.SimpleException;

import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * 单次请求箱
 *
 * @author shaopeng
 * @date 2022/8/8
 */
public class RequestBox {

    /**
     * 开始时间
     */
    private final long beginTime = System.currentTimeMillis();

    /**
     * 超时
     */
    private Integer timeout;



    private final Map<String, JSONObject> onceCacheObj = new HashMap<>();

    /**
     * 自带锁
     * 使用时注意解锁
     *
     */
    private transient final ReentrantLock lock = new ReentrantLock();


    /**
     * 计算剩余等待的时间
     * @return
     */
    public Integer calRemainTime(){
        return timeout == null ? null : Math.toIntExact((timeout + System.currentTimeMillis() - beginTime));
    }


    /**
     * 加锁执行
     *
     * @return 非空
     */
    public <V> V lockCall(Callable<V> callable){
        if (callable == null) {
            throw  new SimpleException(SimpleException.PARAM_INVALID,"callable can not be null");
        }
        boolean lockSuccess = false;
        try {
            try {
                lockSuccess = lock.tryLock(5000, TimeUnit.MILLISECONDS);
                if (!lockSuccess) {
                    throw new SimpleException(SimpleException.LOCK_FAIL,"锁定失败");
                }
            } catch (InterruptedException e) {
                throw new SimpleException(SimpleException.LOCK_FAIL, "thread interrupt");
            }
            try {
                return callable.call();
            } catch (Exception e) {
                throw new SimpleException("lockCallError", e.getMessage());
            }
        } finally {
            if (lockSuccess) {
                lock.unlock();
            }
        }
    }

    /**
     * 取或刷新缓存
     *
     * @param group
     * @param key
     * @param supplier
     * @param <T>
     * @return
     */
    public <T> T getOrLoad(String group, String key, Supplier<Container<T>> supplier) {
        if (group == null || key == null) {
            return null;
        }
        JSONObject cache = onceCacheObj.computeIfAbsent(group, (g) -> new JSONObject(new ConcurrentHashMap<>()));
        Container<T> wrapper = cache.getObject(key, Container.class);
        if (wrapper != null) {
            return wrapper.getObj();
        }
        wrapper = supplier.get();
        if (wrapper == null) {
            return null;
        }
        cache.put(key, wrapper);
        return wrapper.getObj();
    }


    /**
     * 设置
     *
     * @param group
     * @param key
     * @param data
     * @param <T>
     * @return
     */
    public void set(String group, String key, Object data) {
        if (group == null || key == null) {
            return;
        }
        onceCacheObj.computeIfAbsent(group, (g) -> new JSONObject(new ConcurrentHashMap<>())).put(key, Container.build(data));
    }


    /**
     * 批量取
     *
     * @param group
     * @param keys
     * @param loader
     * @param <T>
     * @return
     */
    public <T> Map<String, T> batchGet(String group, Collection<String> keys, Function<Collection<String>, Map<String, Container<T>>> loader) {
        if (group == null || keys == null) {
            return new HashMap<>(2);
        }
        JSONObject cache = onceCacheObj.computeIfAbsent(group, (g) -> new JSONObject(new ConcurrentHashMap<>()));
        Map<String, T> result = new HashMap<>();
        Set<String> missKeys = new HashSet<>();
        for (String key : keys) {
            Container wrapper = cache.getObject(key, Container.class);
            if (wrapper == null) {
                missKeys.add(key);
                continue;
            }
            result.put(key, (T) wrapper.getObj());
        }

        if (missKeys.isEmpty()) {
            return result;
        }

        Map<String, Container<T>> loadResult = loader.apply(missKeys);
        if (loadResult == null) {
            return result;
        }
        for (String missKey : missKeys) {
            Container<T> wrapper = loadResult.get(missKey);
            if (wrapper == null) {
                continue;
            }
            cache.put(missKey, wrapper);
            result.put(missKey, wrapper.getObj());
        }
        return result;
    }

    /**
     * 根据类型取唯一缓存
     *
     * @param type
     * @param supplier
     * @param <T>
     * @return
     */
    public <T> T getOrLoadSingle(Class<T> type, Supplier<Container<T>> supplier) {
        if (type == null) {
            return null;
        }
        return getOrLoad("_SINGLE_", type.getName(), supplier);
    }


    /**
     * 根据类型取唯一缓存
     *
     * @param type
     * @param <T>
     * @return
     */
    public <T> T getSingle(Class<T> type) {
        if (type == null) {
            return null;
        }
        JSONObject cache = onceCacheObj.get("_SINGLE_");
        if (cache == null) {
            return null;
        }
        return cache.getObject(type.getName(), type);
    }


    /**
     * 设置唯一缓存
     *
     * @param type
     * @param obj
     * @param <T>
     */
    public <T> void setSingle(Class<T> type, T obj) {
        if (type == null) {
            return;
        }
        JSONObject cache = onceCacheObj.computeIfAbsent("_SINGLE_", (g) -> new JSONObject(new ConcurrentHashMap<>()));
        cache.put(type.getName(), Container.build(obj));
    }

    public Integer getTimeout() {
        return timeout;
    }

    public void setTimeout(Integer timeout) {
        this.timeout = timeout;
    }

    public long getBeginTime() {
        return beginTime;
    }
}
